PHP 7.1 changelog + casos de uso
Continuando con esta serie de post que busca comentar aquellos grandes cambio en favor de mejorar la experiencia de los desarrolladores con el lenguaje, esta vez toca a la versión 7.1 .
Tipos nulleables y void returns
A las opciones para tipos de parámetros de entrada/salida en funciones se adiciona el hecho de poder hacerlos nulleables, es decir llevar el caso utilizable desde el chequeo coercitivo, hacia el estricto introducido en la 7.0.
<?php
declare(strict_types=1);
function suma(?float $a, ?float $b) :?float {
// Si también podriamos escribir esto aprovechandonos de los falsy values de PHP
// Pero, claridad ante todo !
if(is_null($a) || is_null($b)) {
return null;
}
return $a + $b
}
echo suma(2, 4.1);
// 6.1
echo suma(null, 3); echo suma(12, null); echo suma(null, null);
// null
Conjunto a esto, el tema de void para dejar en claro aquellas funciones que no van a retornar nada y que esto también sea verificable mediante chequeo estricto.
<?php
declare(strict_types=1);
function printTextV1(string $message) :void {
echo $message;
}
function printTextV2(string $message) :void {
$MAX_CHARACTERS_LENGTH = 10;
$SLICE_POSITION_FOR_LARGER_STRINGS = 7;
if(strlen($message) <= $MAX_CHARACTERS_LENGTH )
{
echo $message;
return;
}
echo substr($message, 0, $SLICE_POSITION_FOR_LARGER_STRINGS) . "...";
}
function printTextV3(string $message) :void {
echo $message;
return true;
}
printTextV1("Hola Mundo!");
// "Hola Mundo!"
printTextV2("Hola Mundo!");
// "Hola Mu..."
printTextV3("Hola Mundo!");
// Fatal error: A void function must not return a value
En caso de requerir cortar el flujo de ejecución debemos utilizar el return; como en la función printTextV2.
Por otro lado, si intentamos retornar algún tipo de dato (printTextV3) tendremos un error expresivo que nos indique el problema sin necesidad de que el flujo llegue a ser ejecutado (Runtime Error).
ATENCIÓN, a continuación un detalle tramposo de nuestro amigo PHP para el uso de nulleables en las Interfaces
Debemos tener en cuenta que al implementar una interface con métodos que utilicen ~retornos~ nulleable, en otra interface que herede de esta, los retornos pueden ser eliminados y solo dejar el tipo estricto. Mientras, para los ~argumentos~ ocurre lo contrario, no se puede eliminar el tipo nulleable de estos, ¡pero si podemos agregarlo!
<?php
declare(strict_types=1);
// CORRECT EXAMPLE
interface NullableFooable {
public function foo(int $paramOne): ?Fooable;
}
interface StrictFooable extends NullableFooable {
public function foo(?int $paramOne): Fooable; // valid
}
// ----------------------------------------------
// BAD EXAMPLE
interface Fooable {
public function foo(?int $paramOne): Fooable;
}
interface NullableFooable extends Fooable {
public function foo(int $paramOne): ?Fooable; // Invalid
}
Destructuración de Arrays
Hasta esta versión la destructuración debía hacerse utilizando la función list
<?php
$commonArray = [1, 2, 3];
list($a, $b, $c) = $commonArray;
var_dump($a, $b, $c); // int(1) int(2) int(3)
Cuando intentamos realizarlo con arrays asociativos, con claves no numéricas, la cuestión cambia un poco
<?php
$arrayWithStringKeys = ["a" => 1, "b" => 2, "c" => 3];
list($a, $b, $c) = $arrayWithStringKeys;
var_dump($a, $b, $c); // NULL NULL NULL
list("a"=>$a, "b"=>$b, "c"=>$c) = $arrayWithStringKeys;
var_dump($a, $b, $c); // int(1) int(2) int(3)
Afortunadamente en esta versión nos permite una forma de gestionarlo bastante "flexible", pero todavía falta el toque final. Podemos utilizar la notación de arrays y evitar si queremos usar la función list
<?php
[$a, $b, $c] = $commonArray;
var_dump($a, $b, $c); // int(1) int(2) int(3)
["a"=>$a, "b"=>$b, "c"=>$c] = $arrayWithStringKeys;
var_dump($a, $b, $c); // int(1) int(2) int(3)
// Por si surje la duda, aplica en forma multinivel
[[$x1, $y1], [$x2, $y2]] = [[7, 2], [9, 4]];
var_dump($x1, $y1, $x2, $y2); // int(7) int(2) int(9) int(4)
[["x"=>$x1], ["x"=>$x2]] = [["x"=>7, "y"=>2], ["x"=>9, "y"=>4]];
var_dump($x1, $x2); // int(7) int(9)
'Tipo' Iterable
Este nuevo seudo tipo fue adicionado para poder unificar en el type-hint la recepción/retorno de arrays u objetos que implementen la interface Travesable bajo un mismo 'tipo'. Esto nos permite abstraer un poco más nuestro código y sabe que lo recibido podrá ser recorrido mediante un foreach o con yield desde un generator.
Hay aquí un detalle a remarcar, Traversable es una interface no implementable, similar a caso de Throwable que comente en el post anterior. Enfatizo lo de similar, ya que Throwable se implementa heredando las clases Error/Exception, ya veremos que pasa con Traversable. Para hacer uso de ella se lo hace mediante interfaces que la extienden como son IteratorAggregate y Iterator.
<?php
// PHP 7.1
function echoIterable(iterable $items)
{
foreach($items as $item){
echo $item . PHP_EOL;
}
}
echoIterable([1, 2, 3]);
// 1
// 2
// 3
echoIterable(new IntegerCollection(1, 2, 3));
// 1
// 2
// 3
Vale aclarar que IntegerCollection no es una clase que exista por defecto en PHP, sino que es una custome para el caso de ejemplo, aquí muestro dos ejemplos de implementación cada una usando una de las interfaces de Traversable:
Con la interface IteratorAggregate
<?php
class IntegerCollection implements IteratorAggregate {
public $values = [];
public function __construct(int ...$integerValues) {
$this->values = $integerValues;
}
public function getIterator() :Traversable {
return new ArrayIterator($this->values);
}
}
Con la interface Iterator
<?php
class IntegerCollection implements Iterator {
private $position = 0;
public $values = [];
public function __construct(int ...$integerValues) {
$this->values = $integerValues;
$this->rewind();
}
public function rewind() :void {
$this->position = 0;
}
public function current() {
return $this->values[$this->position];
}
public function key() {
return $this->position;
}
public function next() :void {
$this->position++;
}
public function valid() :bool {
return array_key_exists( $this->key(), $this->values);
}
}
Accesor de visibilidad para Constantes en una Clase
Anteriormente las constantes definidas en una clase tenían una visibilidad publica en forma natural y forzosa, ya que no se tenia la posibilidad de cambiarlo.
Esto, aunque aparentemente un cambio pequeño es una gran ayuda a permitirnos un mayor encapsulamiento pensado desde la Programación Orientada a Objetos y en segunda instancia, nos permite determinar en forma clara que se espera de esa constante, que se utilice desde fuera de la clase en alguna situación que lo justifique (public), que una clase que herede nuestro pueda redefinir su valor para si misma (protected), o bien, que solo sea utilizable desde la misma clase(private).
Como aspecto seguro, de aquí en más ver una const sin que la preceda un accesor de visibilidad, debería mirarse por lo menos raro(malo).
<?php
class SomeClass {
// Como ya lo venía siendo, por defecto es public
const PUBLIC_CONST = 0;
// PERO evitemos no ponerle su accesor, el que lo tenga nos brinda un paneo general de que poder esperar de ella
// Como entiendo deberiamos utilizarlas
private const PRIVATE_CONST = 0;
protected const PROTECTED_CONST = 0;
public const PUBLIC_CONST_TWO = 0;
// Bajo un mismo accesor de visibilidad podemos declarar una lista de ellas
private const PRIVATE_CONST_A = 1, PRIVATE_CONST_B = 2;
}
Captura de Excepciones múltiples en un paso
Esta es una habilidad que en otros lenguajes como Java ya existen, la posibilidad de en un mismo bloque Catch procesar diferentes excepciones. Esto suele ser util para poder ser un poco más DRY en casos donde para excepciones diferentes el procesamiento de la falla es el mismo.
<?php
try{
// somthing
} catch(MissingParameterException $e) {
throw new \InvalidArgumentException($e->getMessage());
} catch(IllegalOptionException $e) {
throw new \InvalidArgumentException($e->getMessage());
}
// A partir de PHP 7.1, utilizando el operador 'pipe' podemos re-escribirlo como:
try {
// something
} catch (MissingParameterException | IllegalOptionException $e) {
throw new \InvalidArgumentException($e->getMessage());
}
DISCLAIMER: Seguramente algún detalle nuevo se me paso por alto (intencionalmente ??? o tal vez no), pero inteté cubrir a mi parecer lo mas relevante o lo que más útil fue para mi labor diario, para el resto ~~MasterCard~~, no mentira fuera de broma el changelog completo lo pueden ver en la página oficial de PHP aquí.